use crate::mqi;
use std::ffi::{CString, c_void };
use failure::Error;
use crate::{MqError, strcpy};
use crate::{ComplCode, ReasonCode,
            set_mqcd_default, set_mqcno_default};

#[derive(Debug)]
pub struct MQueueManager {
    conn_handler: i32,
    name: String,
    channel: String,
    host: String,
}

impl MQueueManager {
    pub fn name(&self) -> &str{
        &self.name
    }
    pub fn connect(name: &str, channel: &str, host: &str,
                   user: &str, password: Option<&str>) -> Result<MQueueManager, Error> {
        let qm_name = CString::new(name)?;
        let mut cc = -1;
        let mut rc = -1;
        let mut chan_def = MQChannelDefBuilder::default()
            .conn_name(host)
            .channel_name(channel)
            .qm_name(name)
            .password(password.unwrap_or(""))
            .user_name(user)
            .build();
        let mut conn_opts = MQConnOptsBuilder::default()
            .conn_ptr(&mut chan_def)
            .build();
        let mut conn_handler = 0;
        unsafe {
            mqi::MQCONNX(qm_name.into_raw(), &mut conn_opts as *mut mqi::MQCNO,
                          &mut conn_handler as *mut i32,
                          &mut cc, &mut rc);
        }
        let cc = ComplCode(cc);
        let rc = ReasonCode(rc); 
        if cc.is_ok() && rc.is_ok() {
            info!("Connected to {:?} on {:?}, ", name, host);
            Ok(MQueueManager{name: name.to_owned(),
                             channel: channel.to_owned(),
                             host: host.to_owned(), conn_handler})
        } else {
            error!("Error connecting to qm {:?}, compl_code: {}, reason: {}",
                   name, cc, rc);
            Err(Error::from(MqError::ConnectFailed{ cc, rc }))
        }
        
    }
   
    pub fn disconnect(&self){
        let mut cc = -1;
        let mut rc = -1;
        unsafe {
            // we do not want MQ to be mutable because of disconnect binding
            let mut c_handle = self.conn_handler;
            mqi::MQDISC(&mut c_handle, &mut cc as *mut i32, &mut rc as *mut i32);
        };
        let cc = ComplCode(cc); 
        let rc = ReasonCode(rc); 
        if  cc.is_ok() && rc.is_ok() {
            info!("Disconnected from QM {}", self.name);
        } else { 
            panic!("Error disconecting from QM {}, CC: {}, RC: {}", self.name, cc, rc);
        }
    }

    pub fn conn_handler(&self) -> &i32{
        &self.conn_handler
    }
}

pub struct MQOpenOpts {
    opts: i32
}

impl MQOpenOpts {
    pub fn new() -> MQOpenOpts {
        MQOpenOpts{opts: 0}
    }
    pub fn builder() -> MQOpenOptsBuilder {
        MQOpenOptsBuilder::new()
    }
}

impl Into<i32> for MQOpenOpts {
    fn into(self) -> i32 {
        self.opts
    }
}

pub struct MQOpenOptsBuilder {
    opts: i32
}

impl MQOpenOptsBuilder {
    pub fn new() -> MQOpenOptsBuilder {
        MQOpenOptsBuilder{opts: 0}
    }
    pub fn build(&self) -> MQOpenOpts {
        MQOpenOpts{opts: self.opts}
    }
    pub fn with_option(&mut self, opt: i32) -> &mut Self{
        self.opts += opt;
        self
    }
}

#[derive(Debug, Clone)]
struct MQConnOptsBuilder { 
    mqcno: mqi::MQCNO,
}

impl MQConnOptsBuilder {
    fn new() -> MQConnOptsBuilder {
        let mut mqcno: mqi::MQCNO = unsafe { std::mem::zeroed() };
        unsafe {
            set_mqcno_default(&mut mqcno as *mut mqi::MQCNO);
        }
        MQConnOptsBuilder{mqcno}
    }
    fn version(&mut self, version: i32) -> &mut Self {
        self.mqcno.Version = version;
        self
    }
    fn conn_ptr(&mut self, mqcd: &mut mqi::MQCD) -> &mut Self {
        self.mqcno.ClientConnPtr = mqcd as *mut _ as *mut c_void;
        self
    }
    fn build(&self) -> mqi::MQCNO {
        self.mqcno
    }
}

impl Default for MQConnOptsBuilder {
    fn default() -> MQConnOptsBuilder {
        MQConnOptsBuilder::new()
            .version(mqi::MQCNO_VERSION_6).clone()
    }
}

#[derive(Debug, Clone)]
struct MQChannelDefBuilder {
    mqcd: mqi::MQCD
}

impl MQChannelDefBuilder {
    fn new() -> MQChannelDefBuilder {
        let mut mqcd: mqi::MQCD = unsafe { std::mem::zeroed() };
        unsafe {
            set_mqcd_default(&mut mqcd as *mut mqi::MQCD );
        }
        MQChannelDefBuilder{mqcd}
    }
    fn build(&self) -> mqi::MQCD {
        self.mqcd 
    }
    fn version(&mut self, version: i32) -> &mut Self {
        self.mqcd.Version = version;
        self
    }
    fn conn_name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqcd.ConnectionName, name.len());
        self
    }
    fn channel_name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqcd.ChannelName, name.len());
        self
    }
    fn channel_type(&mut self, mqcht: i32) -> &mut Self {
        self.mqcd.ChannelType = mqcht;
        self
    }
    fn transport_type(&mut self, mqxpt: i32) -> &mut Self {
        self.mqcd.TransportType = mqxpt;
        self
    }
    fn qm_name(&mut self, name: &str) -> &mut Self {
        strcpy(name.as_bytes(), &mut self.mqcd.QMgrName, name.len());
        self
    }
    fn user_name(&mut self, user_name: &str) -> &mut Self {
        strcpy(user_name.as_bytes(), &mut self.mqcd.UserIdentifier, user_name.len());
        self
    }
    fn password(&mut self, password: &str) -> &mut Self {
        strcpy(password.as_bytes(), &mut self.mqcd.Password, password.len());
        self
    }
}

impl Default for MQChannelDefBuilder {
    fn default() -> MQChannelDefBuilder {
        MQChannelDefBuilder::new()
            .channel_type(mqi::MQCHT_CLNTCONN)
            .transport_type(mqi::MQXPT_TCP)
            .clone()
    }
}

